package com.ihr360.payroll.service.salarytasklet.step.reader;

import com.google.common.collect.Lists;
import com.ihr360.common.DateRange;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.DateUtils;
import com.ihr360.job.core.annotation.BeforeStep;
import com.ihr360.job.core.entity.StepExecution;
import com.ihr360.job.core.item.ItemReader;
import com.ihr360.job.core.item.NonTransientResourceException;
import com.ihr360.job.core.item.ParseException;
import com.ihr360.job.core.item.UnexpectedInputException;
import com.ihr360.job.core.item.support.IteratorItemReader;
import com.ihr360.job.core.item.support.ListItemReader;
import com.ihr360.payroll.config.SalaryTaxTableEntity;
import com.ihr360.payroll.dao.ActivePayPeriodRepository;
import com.ihr360.payroll.dao.AttendancePlanRepository;
import com.ihr360.payroll.dao.ReferenceServiceRepository;
import com.ihr360.payroll.dao.SalaryPlanStaffRepository;
import com.ihr360.payroll.dao.SalaryTaxTableRepository;
import com.ihr360.payroll.dao.SystemSalaryFieldRepository;
import com.ihr360.payroll.model.dto.attendance.plan.AttendancePlan;
import com.ihr360.payroll.model.dto.reference.ReferenceService;
import com.ihr360.payroll.model.dto.salary.meta.system.SystemSalaryField;
import com.ihr360.payroll.model.dto.salary.plan.SalaryPlanStaff;
import com.ihr360.payroll.model.dto.salary.task.period.ActivePayPeriod;
import com.ihr360.payroll.model.dto.salary.task.period.PayPeriodTerm;
import com.ihr360.payroll.model.dto.salary.tax.SalaryTaxTable;
import com.ihr360.payroll.service.payroll.PayrollConfigService;
import com.ihr360.payroll.service.payroll.convertor.SalaryTaxTableConvertor;
import com.ihr360.payroll.service.salarytasklet.model.SimplePayrollConfig;
import com.ihr360.payroll.service.salarytasklet.step.SalaryCalcContext;
import com.ihr360.payroll.type.PeriodState;
import com.ihr360.sharedservice.api.StatutoryVacationService;
import com.ihr360.sharedservice.model.StatutoryVacation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author davidwei
 */
@Component
public class StepReader {

    @Autowired
    SalaryTaxTableRepository salaryTaxTableRepository;


    @Autowired
    SalaryPlanStaffRepository salaryPlanStaffRepository;

    @Autowired
    private SalaryTaxTableConvertor salaryTaxTableConvertor;

    public ItemReader<SalaryTaxTableEntity> taxTableReader() {

        return new ListItemReader<SalaryTaxTableEntity>() {
            private boolean hasRead = false;

            @Override
            public SalaryTaxTableEntity read() {
                if (!hasRead) {
                    List<SalaryTaxTable> tables = salaryTaxTableRepository.findByEnabled(Boolean.TRUE);
                    if (CollectionUtils.isEmpty(tables)) {
                        throw new Ihr360Exception(null, "无法获取可用的税表配置信息");
                    }
                    List<SalaryTaxTableEntity> taxTableEntities = tables.stream().map(t -> salaryTaxTableConvertor.convertDtoToEntity(t)).collect(Collectors.toList());
                    setList(taxTableEntities);
                    hasRead = true;
                }
                return super.read();
            }
        };
    }

    @Autowired
    SystemSalaryFieldRepository systemSalaryFieldRepository;

    @Autowired
    ActivePayPeriodRepository activePayPeriodRepository;

    public ItemReader<SystemSalaryField> systemSalaryFieldReader(final Long payperiodId) {

        return new IteratorItemReader<SystemSalaryField>() {
            private boolean hasRead = false;

            @Override
            public SystemSalaryField read() throws Exception {
                if (!hasRead) {

                    Iterable<SystemSalaryField> systemSalaryFieldsIterable = systemSalaryFieldRepository.findAll();
                    setIterator(systemSalaryFieldsIterable);
                    hasRead = true;
                }
                return super.read();
            }

            @BeforeStep
            public void saveStepExecution(StepExecution stepExecution) {
                ActivePayPeriod payPeriod = activePayPeriodRepository.findOne(payperiodId);
                payPeriod.setState(PeriodState.CALCULATING);
                PayPeriodTerm currentTerm = payPeriod.getCurrentTerm();
                currentTerm.setState(PeriodState.CALCULATING);
                activePayPeriodRepository.save(payPeriod);
            }
        };
    }

    public ItemReader<ActivePayPeriod> activePayPeriodReader(final Long payperiodId) {
        return new ItemReader<ActivePayPeriod>() {
            private boolean hasRead = false;

            @Override
            public ActivePayPeriod read() throws Exception {
                if (!hasRead) {
                    ActivePayPeriod payPeriod = activePayPeriodRepository.findOne(payperiodId);
                    hasRead = true;
                    return payPeriod;
                }
                return null;
            }
        };
    }


    public ItemReader<SalaryPlanStaff> salaryPlanStaffsReader(final Long salaryPlanId) {

        return new ListItemReader<SalaryPlanStaff>() {
            private boolean hasRead = false;

            @Override
            public SalaryPlanStaff read() {
                if (!hasRead) {
                    List<SalaryPlanStaff> staffs = salaryPlanStaffRepository
                            .findBySalaryPlanIdAndEnabledAndStaffStateIn(salaryPlanId, Boolean.TRUE,
                                    Lists.newArrayList(SalaryPlanStaff.StaffState.NORMAL, SalaryPlanStaff.StaffState.TEMP_ADD));
                    setList(staffs);
                    hasRead = true;
                }
                return super.read();
            }
        };
    }

    public ItemReader<SalaryPlanStaff> salaryPlanStaffsReader(final List<String> ids) {

        return new ListItemReader<SalaryPlanStaff>() {
            private boolean hasRead = false;

            @Override
            public SalaryPlanStaff read() {
                if (!hasRead) {
                    List<SalaryPlanStaff> staffs = ids.stream().map(s -> {
                        SalaryPlanStaff staff = new SalaryPlanStaff();
                        staff.setStaffId(s);
                        return staff;
                    }).collect(Collectors.toList());
                    setList(staffs);
                    hasRead = true;
                }
                return super.read();
            }
        };
    }

    public ItemReader<List<String>> mergedSalaryPlanStaffsReader(final List<Long> salaryPlanIds) {
        return new ListItemReader<List<String>>() {
            private static final int CHUNK_SIZE = 50;
            private int cursor = 0;
            private List<List<String>> partitions;
            @Override
            public List<String> read() {
                if (cursor == 0) {
                    List<Long> planIds = salaryPlanIds.stream().filter(l -> l != null).collect(Collectors.toList());
                    List<SalaryPlanStaff> staffs = salaryPlanStaffRepository
                            .findBySalaryPlanIdInAndEnabledAndStaffStateIn(planIds, Boolean.TRUE,
                                    Lists.newArrayList(SalaryPlanStaff.StaffState.NORMAL, SalaryPlanStaff.StaffState.TEMP_ADD));
                    List<String> staffIds = staffs.stream().map(s -> s.getStaffId()).collect(Collectors.toList());
                    List<String> cloneStaffs = new ArrayList<>();
                    staffIds.stream().forEach(s -> cloneStaffs.add(s));
                    this.partitions = Lists.partition(cloneStaffs, CHUNK_SIZE);
                }
                if (cursor >= this.partitions.size()) {
                    return null;
                }
                List<String> subStaffIds = partitions.get(cursor);
                cursor++;
                return subStaffIds;
            }
        };
    }

    public ItemReader<List<String>> mergedSalaryPlanStaffIdsReader(final List<String> staffIds) {
        return new ListItemReader<List<String>>() {
            private static final int CHUNK_SIZE = 50;
            private int cursor = 0;
            private List<List<String>> partitions;
            @Override
            public List<String> read() {
                if (cursor == 0) {
                    List<String> cloneStaffs = new ArrayList<>();
                    staffIds.stream().forEach(s -> cloneStaffs.add(s));
                    this.partitions = Lists.partition(cloneStaffs, CHUNK_SIZE);
                }
                if (cursor >= this.partitions.size()) {
                    return null;
                }
                List<String> subStaffIds = partitions.get(cursor);
                cursor++;
                return subStaffIds;
            }
        };
    }

    @Autowired
    private StatutoryVacationService statutoryVacationService;

    public ItemReader<List<StatutoryVacation>> workingCalendar(DateRange dateRange) {

        return new ItemReader<List<StatutoryVacation>>() {
            private boolean hasRead = false;

            @Override
            public List<StatutoryVacation> read() {
                if (!hasRead) {
                    List<StatutoryVacation> vacations = statutoryVacationService.getStatutoryHolidayVacationBetweenDay(DateUtils.formatDateStr(dateRange.getStart(), DateUtils.STANDARD_DATE_FORMAT_YMD), DateUtils.formatDateStr(dateRange.getEnd(), DateUtils.STANDARD_DATE_FORMAT_YMD));
                    hasRead = true;
                    return vacations;
                }
                return null;
            }
        };
    }

    @Autowired
    AttendancePlanRepository attendancePlanRepository;

    public ItemReader<AttendancePlan> attendancePlanReader(Long attendancePlanId) {
        ItemReader<AttendancePlan> attendancePlanReader = new ItemReader<AttendancePlan>() {
            private boolean hasRead = false;

            @Override
            public AttendancePlan read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
                if (hasRead) {
                    return null;
                }
                AttendancePlan attendancePlan = attendancePlanRepository.findOne(attendancePlanId);
                hasRead = true;
                return attendancePlan;
            }
        };
        return attendancePlanReader;
    }

    @Autowired
    private PayrollConfigService payrollConfigService;

    public ItemReader<SimplePayrollConfig> payrollConfigReader(String companyId, Long payrollConfigId) {
        return new ItemReader<SimplePayrollConfig>() {
            private boolean hasRead = false;
            StepExecution stepExecution;

            @Override
            public SimplePayrollConfig read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
                if (hasRead) {
                    return null;
                }
                //TODO://异常发生了怎么办
                SimplePayrollConfig payrollConfig = payrollConfigService.getSimplePayRollConfig(companyId, payrollConfigId);

                hasRead = true;
                return payrollConfig;
            }

            @BeforeStep
            public void saveStepExecution(StepExecution stepExecution) {
                this.stepExecution = stepExecution;
            }
        };
    }

    @Autowired
    private ReferenceServiceRepository referenceServiceRepository;

    public ItemReader<List<ReferenceService>> referenceServicesReader() {
        return new ItemReader<List<ReferenceService>>() {
            private boolean hasRead = false;
            StepExecution stepExecution;

            @Override
            public List<ReferenceService> read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
                if (hasRead) {
                    return null;
                }
                //TODO://异常发生了怎么办
                Iterable<ReferenceService> referenceServices = referenceServiceRepository.findAll();
                List<ReferenceService> list = new ArrayList<>();
                referenceServices.forEach(list::add);
                hasRead = true;
                return list;
            }
        };
    }

    public ItemReader<SalaryCalcContext> salaryCalcContextItemReader(SalaryCalcContext salaryCalcContext) {
        return new ItemReader<SalaryCalcContext>() {
            private boolean hasRead = false;

            @Override
            public SalaryCalcContext read() throws Exception, ParseException, NonTransientResourceException {
                if (hasRead) {
                    return null;
                }
                hasRead = true;
                return salaryCalcContext;
            }
        };
    }

}